/**
* @author Meng Zhang
**/

#include "stdafx.h"
#include "CodeEdit.h"
#include "../TextBuffer/TextBuffer.h"
#include "../ConfigInfo/ConfigInfo.h"
#include "../TextBuffer/TextBufferView.h"

CCodeEdit::CCodeEdit(void) {
}

CCodeEdit::~CCodeEdit(void) {
}

struct SScintillaColors
{   
	int         iItem;
	COLORREF    rgb;
};

#define CMPRETURN(X,Y) do{ \
	if (c==std::string(#X))	\
	return Y;	\
}while(0)

COLORREF C(const std::string & c) {
	CMPRETURN(black,RGB(0,0,0));
	CMPRETURN(white,RGB(255,255,255));
	CMPRETURN(green,RGB(0,255,0));
	CMPRETURN(red,RGB(255,0,0));
	CMPRETURN(blue,RGB(0,0,255));
	CMPRETURN(yellow,RGB(255,255,0));
	CMPRETURN(magenta,RGB(255,0,255));
	CMPRETURN(cyan,RGB(0,255,255));
	CMPRETURN (brown,RGB(255,221,0));
	return 0;
}

// static SScintillaColors rgbSyntaxCpp[] = 
// {
// 	{   SCE_C_COMMENT,          C("green") },
// 	{   SCE_C_COMMENTLINE,      C("green") },
// 	{   SCE_C_COMMENTDOC,       C("green") },
// 	{   SCE_C_NUMBER,           C("magenta") },
// 	{   SCE_C_STRING,           C("yellow") },
// 	{   SCE_C_CHARACTER,        C("yellow") },
// 	{   SCE_C_UUID,             C("cyan") },
// 	{   SCE_C_OPERATOR,         C("red") },
// 	{   SCE_C_PREPROCESSOR,     C("cyan") },
// 	{   SCE_C_WORD,             C("cyan") },
// 	{   -1,                     0 }
// };

void CCodeEdit::RecalcLineMargin() {
	SetMarginWidthN(0,0);
	SetMarginWidthN(1,0);
	//SetMarginWidthN(2,0);
	SetMarginTypeN(0,SC_MARGIN_NUMBER);
	//SetMarginTypeN(1,SC_MARGIN_SYMBOL);
	//SetMarginTypeN(2,SC_MARGIN_SYMBOL);

	TCHAR buffer[1024] = {0};
	int lines = GetLineCount();
	_stprintf_s(buffer, TEXT("_%d"), lines);

	int size = TextWidth(STYLE_LINENUMBER,buffer);
	SetMarginWidthN(0, size);
	/*SetMarginWidthN(SCI_SETMARGINWIDTHN,0);*/
}

void CCodeEdit::InitalizeWithConfigInfo() {
	//Default Config
	SetHScrollBar(FALSE);
	SetVScrollBar(FALSE);
	StyleClearAll();
	SetBackSpaceUnIndents(TRUE);
	SetTabIndents(TRUE);
	SetIndentationGuides(SC_IV_LOOKBOTH);

	// 	SetProperty("fold","1");
	// 	SetProperty("fold.compact","0");
	// 	SetProperty("fold.comment","1");
	// 	SetProperty("fold.preprocessor","1");
	// 	SetMarginTypeN(MARGIN_SCRIPT_FOLD_INDEX,SC_MARGIN_SYMBOL);
	// 	SetMarginMaskN(MARGIN_SCRIPT_FOLD_INDEX,SC_MASK_FOLDERS);
	// 	SetMarginWidthN(MARGIN_SCRIPT_FOLD_INDEX,20);
	// 	
	// 	MarkerDefine(SC_MARKNUM_FOLDER,SC_MARK_PLUS);
	// 	MarkerDefine(SC_MARKNUM_FOLDEROPEN,SC_MARK_MINUS);
	// 	MarkerDefine(SC_MARKNUM_FOLDEREND,SC_MARK_EMPTY);
	// 	MarkerDefine(SC_MARKNUM_FOLDERMIDTAIL,SC_MARK_EMPTY);
	// 	MarkerDefine(SC_MARKNUM_FOLDEROPENMID,SC_MARK_EMPTY);
	// 	MarkerDefine(SC_MARKNUM_FOLDERSUB,SC_MARK_EMPTY);
	// 	MarkerDefine(SC_MARKNUM_FOLDERTAIL,SC_MARK_EMPTY);
	// 	SetFoldFlags(16);
	// 	SetMarginSensitiveN(MARGIN_SCRIPT_FOLD_INDEX,TRUE);

	SetStyleBits(5);

	//from info
	SetLexer(info.Lexer);
	SetUseTabs(info.UseTabs);

	for (int i = 0;i<(int)info.Words.size();++i) {
		SetKeyWords(info.Words[i].item,info.Words[i].words.c_str());
		if (info.Words[i].item<=1)
			AddWords(info.Words[i]);
	}

	SetTabWidth(info.TabWidth);

	StyleSetFont(STYLE_DEFAULT,info.Font.c_str());

	StyleSetFore(STYLE_DEFAULT,C(info.ForeColor));
	SetCaretFore(C(info.CaretForeColor));
	StyleClearAll();
	StyleSetBack(STYLE_DEFAULT,C(info.BackColor));
	for (std::vector<EditColors>::iterator iter = info.ColorMap.begin();iter!=info.ColorMap.end();++iter) {
		StyleSetFore(iter->item,C(iter->rgb));
		StyleSetBack(iter->item,C(info.BackColor));
	}

	SetCaretLineVisible(info.CaretLineVisible);
	SetCaretLineBack(C(info.CaretLineBack));
	RecalcLineMargin();
}

namespace {
	CString   GetSuffix(CString   strFileName)   
	{   
		return   strFileName.Right(strFileName.GetLength()-strFileName.ReverseFind('.')-1);   
	}
}

void CCodeEdit::Initialize(const TextBuffer& buffer) {
	//retreve config info xml
	Events::RetrieveConfigInfoEvent e;

	//cstring convert to char
	CT2CA x(GetSuffix(buffer.PathFilename()).GetString());
	e.fileType = x;

	EventBus::Instance()->PublishEvent(&e);

	ConfigInfoSerializer serializer;
	ConfigInfo &info = serializer.info;
	SXP::CParser parser(&serializer);
	SXP::ErrCode err = parser.FeedFile((char*)e.configFile.c_str());
	if( err != SXP::err_no_error ) {
		WriteLog("\nOops, error \"%s\" at line %d, char %d",
			parser.GetErrorStr().c_str(),
			parser.GetErrorLine(),
			parser.GetErrorCol());
	}
	serializer.MakeColors();
	this->info = info;
	InitalizeWithConfigInfo();

	// 	SXP::CFileOutStream *o = new SXP::CFileOutStream("test.xml");
	// 	SXP::dict attribs;
	// 	serializer.WriteElement(o,attribs);
	// 	delete o;
}

// int CCodeEdit::GetLineLength(int line) {
// 	return GetLineEndPosition(line) - LineFromPosition(line);
// }
// 
// int CCodeEdit::GetCurrentLineNumber() {
// 	return LineFromPosition(GetCurrentPos());
// }
void CCodeEdit::BraceComplete(char ch) {
	char toClose[2] = {0,0};
	if (ch=='(')
		toClose[0] = ')';
	else if (ch=='{')
		toClose[0] = '}';
	else if (ch=='\'')
		toClose[0] = '\'';
	else if (ch=='\"')
		toClose[0] = '\"';

	if (toClose[0]) {
		int pos = GetCurrentPos();
		ReplaceSel(toClose);
		SetSel(pos,pos);
	}
}

namespace {
	bool ValidComplete(char ch) {
		return isalpha(ch) || ch=='_';
	}
}

void CCodeEdit::CharAdded( char ch ) {
	int selEnd = GetSelectionEnd();
	int selStart = GetSelectionStart();

	if (selEnd==selStart && selStart>0) {
		StartAutoIndent(ch);
		
		if (info.TriggerChars.find(ch)!=info.TriggerChars.npos) {
			AutoCCancel();
			StartAutoCompleteField();
		}

		if (ValidComplete(ch) && !AutoCActive())
			StartAutoComplete();

		BraceComplete(ch);
	}
}

namespace {
	std::string GetNearestWords( std::vector<std::string>& wordList , const std::string &pre ) {
		std::string ret;
		for (std::vector<std::string>::iterator iter = wordList.begin();iter!=wordList.end();++iter) {
			if (iter->length()<pre.length()) continue;
			bool flag = true;
			for (int i = 0;i<(int)pre.length();++i)
				if (pre[i]!=(*iter)[i]) {
					flag = false;
					break;
				}
				if (flag)
					ret += (*iter + ' ');
		}
		if (ret.length()) {
			ret.erase(ret.end()-1);
		}
		return ret;
	}
	void CleanList(std::vector<std::string>& wordList) {
		std::sort(wordList.begin(),wordList.end());
		wordList.resize(std::unique(wordList.begin(),wordList.end())-wordList.begin());
	}
}

void CCodeEdit::StartAutoComplete() {
	std::string line = CT2CA(GetCurLine().GetString());
	int current = GetCaretInLine();

	int startword = current;

	while(startword > 0 && ValidComplete(line[startword-1])) {
		--startword;
	}

	std::string root = line.substr(startword,current-startword);

	Events::CompletePrefixEvent e;
	e.prefix = root;
	e.path = GetTextBuffer()->GetProjectPath();
	e.targetFile = CT2CA(GetTextBuffer()->PathFilename().GetString());
	e.wordList=&userList;
	userList.clear();
	EventBus::Instance()->PublishEvent(&e);

	std::vector<std::string> tmp;
	tmp.resize(userList.size()+wordList.size());
	std::copy(userList.begin(),userList.end(),tmp.begin());
	std::copy(wordList.begin(),wordList.end(),tmp.begin()+userList.size());
	CleanList(tmp);

	std::string words;
	if (tmp.size()) {
		words = GetNearestWords(tmp,root);
	}

	if (words.length()==root.length()) {
		AutoCCancel();
	} else if (words.length()) {
		AutoCShow(root.length(),words.c_str());
	}

}

int CCodeEdit::GetCaretInLine() {
	int caret = GetCurrentPos();
	int line = LineFromPosition(caret);
	int lineStart = PositionFromLine(line);
	return caret - lineStart;
}

void CCodeEdit::AddWords( const KeyWords& k ) {
	std::string::const_iterator iter = k.words.begin();
	std::string word;
	while(iter!=k.words.end()) {
		if (*iter==' ') {
			if (word.length())
				wordList.push_back(word);
			word = "";
		} else {
			word+=*iter;
		}
		++iter;
	}
	CleanList(wordList);
}

CTextBufferView* CCodeEdit::GetView() {
	return ((CTextBufferView*)GetParent());
}

TextBuffer* CCodeEdit::GetTextBuffer() {
	return GetView()->GetTextBuffer();
}

void CCodeEdit::StartAutoCompleteField() {
	std::string line = CT2CA(GetCurLine().GetString());
	int current = GetCaretInLine();

	while(current > 0 && info.TriggerChars.find(line[current-1])!=info.TriggerChars.npos) {
		--current;
	}

	int startword = current;

	while(startword > 0 && ValidComplete(line[startword-1])) {
		--startword;
	}

	std::string root = line.substr(startword,current-startword);
	Events::CompleteFieldEvent e;
	e.name = root;
	e.path = GetTextBuffer()->GetProjectPath();
	e.targetFile = CT2CA(GetTextBuffer()->PathFilename().GetString());
	e.wordList = &userList;
	userList.clear();
	EventBus::Instance()->PublishEvent(&e);
	CleanList(userList);
	if (userList.size()) {
		std::string words = GetNearestWords(userList,"");
		if (words.length()) {
			AutoCShow(0,words.c_str());
		}
	}
}

void CCodeEdit::StartAutoIndent(char ch) {
	int eolMode = GetEOLMode();
	int curLine = LineFromPosition(GetCurrentPos());
	int lastLine = curLine - 1;
	int indentAmount = 0;

	if (((eolMode == SC_EOL_CRLF || eolMode == SC_EOL_LF) && ch == '\n') ||
		(eolMode == SC_EOL_CR && ch == '\r')) {
			BufferController::Instance()->VirtualSaveBuffer(GetTextBuffer());

			while (lastLine >= 0 && LineLength(lastLine) == 0)
				lastLine--;

			if (lastLine >= 0) {
				indentAmount = GetLineIndentation(lastLine);
			}

			if (indentAmount > 0) {
				LineIndentation(curLine, indentAmount);
			}
	}
}

void CCodeEdit::LineIndentation(int line, int indent) {
	if (indent < 0)
		return;
	int cpMin = GetSelectionStart() , cpMax = GetSelectionEnd();
	int posBefore = GetLineIndentPosition(line);
	SetLineIndentation(line,indent);
	int posAfter = GetLineIndentPosition(line);
	int posDifference = posAfter - posBefore;
	if (posAfter > posBefore) {
		// Move selection on
		if (cpMin >= posBefore) {
			cpMin += posDifference;
		}
		if (cpMax >= posBefore) {
			cpMax += posDifference;
		}
	} else if (posAfter < posBefore) {
		// Move selection back
		if (cpMin >= posAfter) {
			if (cpMin >= posBefore)
				cpMin += posDifference;
			else
				cpMin = posAfter;
		}
		if (cpMax >= posAfter) {
			if (cpMax >= posBefore)
				cpMax += posDifference;
			else
				cpMax = posAfter;
		}
	}
	SetSel(cpMin, cpMax);
}